//
// Created by jiashu on 5/27/19.
//

#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include "nbdsrv.h"

struct exp_async_data {
    int handle;      /**< file descriptor */
};

static int exp_read(struct exp_async_io *this, off_t offset, uint8_t *buf, size_t len)
{
    int fhandle = this->private->handle;
    ssize_t ret = 0;
//    DEBUG("%s: Asked to read %u bytes at %llu.\n", __func__, (unsigned int) len, (unsigned long long) a);

    while (len > 0 && (ret = pread(fhandle, buf, len, offset)) > 0) {
        offset += ret;
        buf += ret;
        len -= ret;
    }
    return (ret < 0 || len != 0);
}

static int exp_write(struct exp_async_io *this, off_t offset, const uint8_t *buf, size_t len, bool is_fua)
{
    int fhandle = this->private->handle;
    ssize_t ret = 0;
//    DEBUG("%s: Asked to read %u bytes at %llu.\n", __func__, (unsigned int) len, (unsigned long long) a);

    while (len > 0 && (ret = pwrite(fhandle, buf, len, offset)) > 0) {
        offset += ret;
        buf += ret;
        len -= ret;
    }
    if (is_fua == true)
        fdatasync(fhandle);
    return (ret < 0 || len != 0);
}

static int exp_trim(struct exp_async_io *this, off_t offset, size_t len)
{
    int mode = (uint32_t) FALLOC_FL_PUNCH_HOLE | (uint32_t) FALLOC_FL_KEEP_SIZE;
    fallocate(this->private->handle, mode, offset, len);
    return 0;
}

static int stand_submit_io(struct exp_async_io *this, struct expand_sqe *sqe)
{
    int fhandle = this->private->handle;
    if (sqe->opcode == NBD_CMD_READ) {
        ssize_t ret = 0;
        size_t len = sqe->len;
        uint8_t *buf = (void *) sqe->user_data;
        uint64_t offset = sqe->offset;
        while (len > 0 && (ret = pread(fhandle, buf, len, offset)) > 0) {
            offset += ret;
            buf += ret;
            len -= ret;
        }
        return (ret < 0 || len != 0);
    }

}

static int exp_flush(struct exp_async_io *this)
{
    fsync(this->private->handle);
    return 0;
}

static size_t exp_get_device_size(struct exp_async_io *this)
{
    off_t es;
    int handle = this->private->handle;
    struct stat stat_buf;
    int error;

    stat_buf.st_size = 0;
    error = fstat(handle, &stat_buf);
    if (!error) {
        /* always believe stat if a regular file as it might really
         * be zero length */
        if (S_ISREG(stat_buf.st_mode) || (stat_buf.st_size > 0))
            return (uint64_t) stat_buf.st_size;
    } else {
        printf("fstat failed: %s\n", strerror(errno));
    }

    es = lseek(handle, (off_t) 0, SEEK_END);
    if (es > ((off_t) 0)) {
        return (uint64_t) es;
    } else {
        printf("lseek failed: %d\n", errno);
    }

    printf("Could not find size of exported block device: %s\n", strerror(errno));
    return UINT64_MAX;
}

static int exp_open(struct exp_async_io *this, const char *device_name)
{
    this->private = malloc(sizeof(struct exp_async_io));

    printf("Opening device/file: %s\n", device_name);
    int handle = open(device_name, O_RDWR, 0600);
    if (handle < 0)
        return -1;

    this->private->handle = handle;
    return 0;
}

static int exp_close(struct exp_async_io *this)
{
    close(this->private->handle);
    free(this->private);
    return 0;
}

const struct exp_async_io expand_standard = {
        .open = exp_open,
        .close = exp_close,
        .get_device_size = exp_get_device_size,
        .submit_io = stand_submit_io,
//        .write = exp_write,
//        .read = exp_read,
//        .trim = exp_trim,
        .flush = exp_flush,
};
